iT邦幫忙

2021 iThome 鐵人賽

DAY 14
0

元件介紹

Card 是一個可以顯示單個主題內容及操作的元件,通常這個主題內容包含圖片、標題、描述或是一些操作。

例如在電商網站,一個商品或需要包含商品圖片、商品名稱、商品價格...等等資訊。在新聞網站,每則新聞也會有新聞的圖片、標題、分類標籤、瀏覽次數...等等資訊。或者我們在瀏覽 Youtube 的時候,每則影片也會有影片的封面圖片、影片標題、影片創作者、觀看次數、上架時間...等等資訊。

參考設計 & 屬性分析

由於卡片的形式會因著需要呈現不同的主題、內容而有不同的需要,因此我們在這邊先做一個基礎的卡片,若未來有希望更多樣的排版,可以再基於這個卡片做延伸。

卡片封面

我們常見的卡片形式通常會附上一張封面圖片,所以會需要一個 props 來讓我們傳入卡片的封面。但除了圖片可以當作卡片的方面以外,有時候也有可能是一個影片,或是一個 carousel,因此在 Antd 的設計並沒有把卡片的封面定死成一定得要是圖片,cover 這個 props 允許我們傳入一個 ReactNode,這樣也可以讓封面擁有不同的可能性。

卡片內容

在 Antd 當中為了讓卡片內容的呈現更加靈活,因此提供一個 Meta 元件,讓我們可以處理卡片的 avatar, title 以及 description,並且這三者的型別都為 ReactNode。

卡片操作

在卡片的底部有時候我們會看見一些按鈕讓我們進行操作,Antd 已經設計好一個固定的樣式,並透過 actions 這個 props 來傳入,很有趣的是,actions 是一個 array of ReactNode,按照這個格式傳入,便能夠幫你處理好這些 action 按鈕的排版。

關於 MUI 卡片

MUI 跟 Antd 設計上我覺得比較不同的是,可能考慮到卡片變化的多樣性,他並不先預設立場覺得你的卡片一定要長怎麼樣,他的設計比較像是他提供了很多的積木,你可以按照你的喜好來拼裝你的卡片,因此我們可以看到 CardMedia 元件來幫助我們處理卡片封面的多媒體,CardContent 來幫助我們處理卡片內容,而 CardActions 來幫我們處理卡片的操作行為。

我覺得 Antd 及 MUI 兩者的設計思維也是不一樣。如果我們需要多一點的彈性,那我們程式碼就很難簡潔的做到,例如像 Antd 那樣傳一個 props 就可以搞定,所以 MUI 才會設計成積木拼裝的卡片元件。但如果想要元件寫起來簡潔一點,那我們勢必會需要對你的樣式預設立場,傳幾個固定型別的 props 進去就能夠迅速做出卡片,但多少會失去一些變化的彈性。

我自己的想法是,我們設計卡片的時候可以學習 MUI 那樣,把每一個區塊拆成可拼裝的積木,所以我們就會擁有一系列卡片相關的元件模組可以來組裝。然後假設我們今天要做的是商品卡片,這個商品卡片勢必是會有一定程度的樣式統一,那我們就用這些積木來拼裝成一個商品卡片,以後的使用方式就是我們能直接將商品資料當作 props 來傳入,就能快速生成商品卡片。

改天我們在會員管理頁需要會員卡片,那我們也能夠重新用這些卡片積木來拼裝出一個會員卡片,以後在會員管理的地方我們就傳入一些會員資料就能夠快速生成這些會員卡片。

所以以上述為例,商品卡片不能直接拿來當作會員卡片使用,因為他們可能樣式上的差異導致很難共用。但是由於下面基礎的積木都是一樣的,那至少我們可以共用這些積木來組裝成不同的卡片。

介面設計

Card

屬性 說明 類型 默認值
variant 變化模式 vertical, horizontal, horizontal-reverse vertical
cover 卡片封面媒體 ReactNode
footer 卡片置底頁尾 ReactNode

Card Meta

屬性 說明 類型 默認值
avatar 頭像 ReactNode
title 卡片標題 ReactNode
description 卡片描述 ReactNode

元件實作

卡片的樣式有千千萬萬種,因此我們希望我們的卡片可以在一定的框架下又保留一些彈性,因此我們的卡片結構會如下:

const Card = ({
  className, cover, variant,
  children, footer, ...props
}) => (
  <StyledCard className={className} $variant={variant} {...props}>
    <Cover className="card__cover">{cover}</Cover>
    <SpaceBetween>
      {children}
      {footer}
    </SpaceBetween>
  </StyledCard>
);

卡片封面媒體

首先我們來看 <Cover />,他是一個「卡片封面媒體」,常見的內容是一個主題圖片,但其實也有機會他會是一個影片,或是一個輪播圖片元件 Carousel...等等。

為了適應不同內容,我們乾脆讓 cover 成為一個 props 從外部傳入,這樣就能夠根據不同情境來變化。

我們這邊 cover 是以圖片為例子,因此使用起來的樣子如下:

<Card
  cover={<img src="https://....jpg" alt="" />}
>
  {children}
</Card>

卡片內容

卡片的內容也是有各種可能,因此類似於 cover 一樣,我們也是讓他由 props 來傳入。
但是如果我們什麼東西都不確定,什麼東西都由 props 傳入,那乾脆就需要元件了不是嗎?

因此 children 當中,我們也可以把一些卡片常見的樣式包成元件,以便重複使用,這邊舉一個例子是 <Meta />

Meta 是一個包含 avatar, title, description 的元件:

這個樣式他很常見,但卻不一定總是會出現,因此把他綁死進 children 就不一定適合每個情境,因此乾脆把 Meta 獨立抽出來,當有需要的時候,再把他放進去 children 裏面,因此我們卡片如果使用 Meta 的話,可以像這樣做:

import Card from '../components/Card';
import Meta from '../components/Card/Meta';

<Card
  cover={<img src="https://....jpg" alt="" />}
>
  <CardContent>
    <Meta
      avatarUrl="https://.../choose1.png"
      title="2021 iThome 鐵人賽"
      description="喚醒心中最強大的鐵人"
    />
    {...其他卡片內容...}
  </CardContent>
</Card>

以下面 Hahow 課程卡片來說,有些課程是募資課,他有募資進度條、開課狀態...等等,另一種類型的卡片可能是非募資課,他會需要標示評價、課程時數、上課同學數、價錢等等。

募資卡片及非募資卡片 cover image、title、avatar 樣式是一樣的,但其他不同的地方,我們可以用上面 Meta 範例一樣的方式來組裝,需要募資進度的時候,就在 children 裡面放入 <Progress />,非募資課,那我們就放入其他課程資料 <CourseInfo /><Price /> ...等等。

這樣的話,我們就能夠適度的共用,又能夠保留適度的彈性。

卡片置底頁尾

我們可以看到 Antd 他也把卡片操作組另外獨立成一個 props 來傳入,而不是放在 children 裏面:

<Card
  cover={...}
  actions={[
    <SettingOutlined key="setting" />,
    <EditOutlined key="edit" />,
    <EllipsisOutlined key="ellipsis" />,
  ]}
>
  {children}
</Card>

Antd 這樣的好處是讓我們可以只傳入 icon 的陣列,就產生下面的操作按鈕。
不過其實有時候我們不想要這樣的樣式,我比較希望是有一個 props 讓我傳入的 element 可以保持置底就好,其他樣式我可以自己另外決定,我覺得這樣的自由度比 actions 大,所以我想要設計成這樣:

<Card
  cover={...}
  footer={(
    <Actions>
      <ThumbUpIcon />
      <ShareIcon />
      <NotificationsIcon />
    </Actions>
  )}
>
  {children}
</Card>

這樣我就可以在底部亂放我想要放的東西啦!

變化模式

除了直式的卡片以外,有時候我們會看到橫式的卡片,橫式的話有時候 cover 在左邊,有些在右邊,因此我定義了三種變化模式 vertical, horizontal, horizontal-reverse

這邊我們可以採用 FormControl 那篇我們有介紹過的 flex 佈局的屬性 flex-direction 來達成:

const verticalStyle = css`
  display: inline-flex;
  flex-direction: column;
`;

const horizontalStyle = css`
  display: flex;
`;

const horizontalReverseStyle = css`
  display: flex;
  flex-direction: row-reverse;
`;

這樣在同樣的 DOM 結構下,我們一樣能夠做到不同方向性的卡片了,以下是我們今天的成果:


Card 元件原始碼:
Source code

Meta 元件原始碼:
Source code

Storybook:
Card


上一篇
【Day13】數據展示元件 - Accordion/Collapse 摺疊面板
下一篇
【Day15】數據展示元件 - Carousel
系列文
30 天擁有一套自己手刻的 React UI 元件庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言